package projectUtils;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;

public class ImageArrayUtil {
	public static byte[][] ImageToArray(BufferedImage image)
	{
		int width = image.getWidth();
		int height = image.getHeight();
		byte[][] array = new byte[width][height];
		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				array[i][j] = (byte) (ColorUtil.RGBToGreyScale(image.getRGB(i, j)));
			}
		}
		return array;
	}
	
	public static BufferedImage PadImage(BufferedImage image, int padX, int padY)
	{
		BufferedImage newImage = new BufferedImage(image.getWidth() + 2 * padX, image.getHeight() + 2 * padY, BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < newImage.getWidth(); i++)
		{
			for (int j = 0; j < newImage.getHeight(); j++)
			{
				newImage.setRGB(i, j, 0xFFFFFFFF);
			}
		}
		for (int i = 0; i < image.getWidth(); i++)
		{
			for (int j = 0; j < image.getHeight(); j++)
			{
				newImage.setRGB(padX + i, padY + j, image.getRGB(i, j));
			}
		}
		return newImage;
	}

	
	public static void ArrayImageToBytes(BufferedOutputStream bos, byte[][] array, BufferedImage image, int x0, int y0) throws IOException
	{
		int width = array.length;
		int height = array[0].length;
		bos.write(ByteUtil.IntToByteArray(width));
		bos.write(ByteUtil.IntToByteArray(height));
		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				if (ByteUtil.ByteToInt(array[i][j]) < 255)
				{
					int rgb = image.getRGB(x0 + i, y0 + j);
					bos.write(ByteUtil.IntToByteArray(rgb));
//					int temp = array[i][j];
//					returnImage.setRGB(i, j, 0xFF << 24 | temp & 0xFF << 16 | temp & 0xFF << 8 | temp & 0xFF);
//					image.setRGB(i, j, array[i][j] == (byte)255 ? (byte)255 : (byte)0);
				}
				else
				{
					bos.write(ByteUtil.IntToByteArray(0xFFFFFFFF));
				}
			}
		}
	}
	
	public static BufferedImage RescaleImageWithBuffer(BufferedImage image, int width, int height)
	{
		double reductionFactor = Math.max((double)image.getWidth() / (double)width, (double)image.getHeight() / (double)height);
		int newWidth = (int) Math.round(image.getWidth() / reductionFactor);
		int newHeight = (int) Math.round(image.getHeight() / reductionFactor);
		BufferedImage scaledImage = RescaleImage(image, newWidth, newHeight);
		BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				bufferedImage.setRGB(i, j, 0xFFFFFFFF);
			}
		}
		for (int i = 0; i < newWidth; i++)
		{
			for (int j = 0; j < newHeight; j++)
			{
				bufferedImage.setRGB(i, j, scaledImage.getRGB(i, j));
			}
		}
		scaledImage = null;
		return bufferedImage;
	}
	
	public static BufferedImage CreateResizedCopy(BufferedImage originalImage, 
    		int scaledWidth, int scaledHeight)
    {
    	int imageType = BufferedImage.TYPE_INT_RGB;
    	BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, imageType);
    	Graphics2D g = scaledBI.createGraphics();
    	g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 
    	g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null); 
    	g.dispose();
    	return scaledBI;
    }
	
	public static BufferedImage CreateSubImage(BufferedImage originalImage, int x0, int x1, int y0, int y1)
	{
		BufferedImage returnImage = new BufferedImage(x1 - x0 + 1, y1 - y0 + 1, BufferedImage.TYPE_INT_RGB);
		for (int i = x0; i <= x1; i++)
		{
			for (int j = y0; j <= y1; j++)
			{
				returnImage.setRGB(i - x0, j - y0, originalImage.getRGB(i, j));
			}
		}
		return returnImage;
	}
	
	public static BufferedImage RescaleImage(BufferedImage image, int width, int height)
	{
		BufferedImage returnImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		double xIncremental = (double)image.getWidth() / (double)width;
		double yIncremental = (double)image.getHeight() / (double)height;
		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				double innerCount = 0;
				int rSumValue = 0;
				int gSumValue = 0;
				int bSumValue = 0;
				double factor;
				double startPointX = MathUtil.roundToDecimals(i * xIncremental, 2);
				double endPointX = MathUtil.roundToDecimals((i + 1) * xIncremental, 2);
				double startPointY = MathUtil.roundToDecimals(j * yIncremental, 2);
				double endPointY = MathUtil.roundToDecimals((j + 1) * yIncremental, 2);
				int startX = (int) Math.floor(startPointX);
				int endX = (int) Math.ceil(endPointX) - 1;
				int startY = (int) Math.floor(startPointY);
				int endY = (int) Math.ceil(endPointY) - 1;
				for (int k = startX; k <= endX; k++)
				{
					for (int l = startY; l <= endY; l++)
					{
						factor = 1;
						if (startX == endX && startY == endY)
						{
							factor = (endPointX - startPointX) * (endPointY - startPointY);
						}
						else if (startX == endX)
						{
							factor = (endPointX - startPointX);
						}
						else if (startY == endY)
						{
							factor = (endPointY - startPointY);
						}
						else
						{
							if (k == startX)
							{
								factor *= startPointX - (double)startX;
							}
							else if (k == endX)
							{
								factor *= (double)endX - endPointX + 1;
							}
							if (l == startY)
							{
								factor *= startPointY - (double)startY;
							}
							else if (l == endY)
							{
								factor *= (double)endY - endPointY + 1;
							}
						}
						innerCount += factor;
						if (k >= image.getWidth() || l >= image.getHeight())
							return null;
						int rgb = image.getRGB(k, l);
						int red   = (rgb >> 16) & 0xff; 
						int green = (rgb >>  8) & 0xff; 
						int blue  = (rgb      ) & 0xff;
						rSumValue += factor * red;
						gSumValue += factor * green;
						bSumValue += factor * blue;
					}
				}
				int rgb = (int) (rSumValue / innerCount);
				rgb = (rgb << 8) + (int)(gSumValue / innerCount);
				rgb = (rgb << 8) + (int)(bSumValue / innerCount);
				returnImage.setRGB(i, j, rgb);
			}
		}
		return returnImage;
	}
	
	public static byte[][] ShiftBasedOnCenterOfMass(byte[][] array)
	{
		double centerOfMassX = 0;
		double centerOfMassY = 0;
		double totalX = 0;
		double totalY = 0;
		
		double[] xProjection = new double[array.length];
		double[] yProjection = new double[array[0].length];
		
		for (int i = 0; i < array.length; i++)
		{
			for (int j = 0; j < array[i].length; j++)
			{
				xProjection[i] += (255.0 - ByteUtil.ByteToInt(array[i][j])) / 255.0;
			}
			xProjection[i] /= (double)array[i].length;
		}
		for (int i = 0; i < array[0].length; i++)
		{
			for (int j = 0; j < array.length; j++)
			{
				yProjection[i] += (255.0 - ByteUtil.ByteToInt(array[j][i])) / 255.0;
			}
			yProjection[i] /= array.length;
		}
		
		for (int i = 0; i < xProjection.length; i++)
		{
			centerOfMassX += (xProjection[i]) * (i + 1);
			totalX += (xProjection[i]);
		}
		for (int i = 0; i < yProjection.length; i++)
		{
			centerOfMassY += (yProjection[i]) * (i + 1);
			totalY += (yProjection[i]);
		}
		
		centerOfMassX /= totalX;
		centerOfMassY /= totalY;
		centerOfMassX --;
		centerOfMassY --;
		
		int shiftX = (int) (array.length / 2.0 - centerOfMassX);
		int shiftY = (int) (array[0].length / 2.0 - centerOfMassY);
		byte[][] returnArray = new byte[array.length][array[0].length];
		
		for (int i = 0; i < array.length; i++)
		{
			for (int j = 0; j < array[i].length; j++)
			{
				returnArray[i][j] = (byte)255;
			}
		}
		
		for (int i = 0; i < array.length; i++)
		{
			for (int j = 0; j < array[i].length; j++)
			{
				if (i + shiftX < 0 || i + shiftX >= array.length
						|| j + shiftY < 0 || j + shiftY >= array[0].length)
				{
					
				}
				else
				{
					returnArray[i + shiftX][j + shiftY] = array[i][j];
				}
					
			}
		}
		
		return returnArray;
	}
	
	public static byte[][] RescaleArrayImage(byte[][] array, int width, int height)
	{
		byte[][] returnArray = new byte[width][height];
		double xIncremental = (double)array.length / (double)width;
		double yIncremental = (double)array[0].length / (double)height;
		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				double innerCount = 0;
				int sumValue = 0;
				double factor;
				double startPointX = MathUtil.roundToDecimals(i * xIncremental, 2);
				double endPointX = MathUtil.roundToDecimals((i + 1) * xIncremental, 2);
				double startPointY = MathUtil.roundToDecimals(j * yIncremental, 2);
				double endPointY = MathUtil.roundToDecimals((j + 1) * yIncremental, 2);
				int startX = (int) Math.floor(startPointX);
				int endX = (int) Math.ceil(endPointX) - 1;
				int startY = (int) Math.floor(startPointY);
				int endY = (int) Math.ceil(endPointY) - 1;
				for (int k = startX; k <= endX; k++)
				{
					for (int l = startY; l <= endY; l++)
					{
						factor = 1;
						if (startX == endX && startY == endY)
						{
							factor = (endPointX - startPointX) * (endPointY - startPointY);
						}
						else if (startX == endX)
						{
							factor = (endPointX - startPointX);
						}
						else if (startY == endY)
						{
							factor = (endPointY - startPointY);
						}
						else
						{
							if (k == startX)
							{
								factor *= startPointX - (double)startX;
							}
							else if (k == endX)
							{
								factor *= (double)endX - endPointX + 1;
							}
							if (l == startY)
							{
								factor *= startPointY - (double)startY;
							}
							else if (l == endY)
							{
								factor *= (double)endY - endPointY + 1;
							}
						}
						innerCount += factor;
						sumValue += factor * ByteUtil.ByteToInt(array[k][l]);
					}
				}
				returnArray[i][j] = (byte) (sumValue / innerCount);
			}
		}
		return returnArray;
	}
	
	public static byte[][] NormalizeArrayValue(byte[][] array)
	{
		int maxValue = 0;
		int minValue = 255;
		byte[][] returnArray = new byte[array.length][array[0].length];
		for (int i = 0; i < array.length; i++)
		{
			for (int j = 0; j < array[i].length; j++)
			{
				int value = ByteUtil.ByteToInt(array[i][j]);
				if (value > maxValue)
				{
					maxValue = value;
				}
				if (minValue > value)
				{
					minValue = value;
				}
			}
		}
//		if (maxValue - minValue < 127)
//			return array;
//		else
		{
			for (int i = 0; i < array.length; i++)
			{
				for (int j = 0; j < array[i].length; j++)
				{
					returnArray[i][j] = (byte) (((double)ByteUtil.ByteToInt(array[i][j]) - minValue) / (maxValue - minValue) * (double)255);
				}
			}
			return returnArray;
		}
	}
	
	public static BufferedImage ArrayToImage(byte[][] array, int x0, int y0)
	{
		int width = array.length;
		int height = array[0].length;
		BufferedImage returnImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				{
					returnImage.setRGB(i, j, array[i][j]);
				}
			}
		}
		return returnImage;
	}
	
	public static BufferedImage ArrayToImage(byte[][] array)
	{
		int width = array.length;
		int height = array[0].length;
		BufferedImage returnImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				Integer intensity = ByteUtil.ByteToInt(array[i][j]);
				returnImage.setRGB(i, j, new Color(intensity, intensity, intensity).getRGB());
			}
		}
		return returnImage;
	}
	
	public static BufferedImage ArrayToImage(byte[][] array, BufferedImage image, int x0, int y0)
	{
		int width = array.length;
		int height = array[0].length;
		BufferedImage returnImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
//				if (ByteUtil.ByteToInt(array[i][j]) < 255)
				{
					int rgb = image.getRGB(x0 + i, y0 + j);
					returnImage.setRGB(i, j, rgb);
//					int temp = array[i][j];
//					returnImage.setRGB(i, j, 0xFF << 24 | temp & 0xFF << 16 | temp & 0xFF << 8 | temp & 0xFF);
//					image.setRGB(i, j, array[i][j] == (byte)255 ? (byte)255 : (byte)0);
				}
//				else
				{
//					returnImage.setRGB(i, j, 0xFFFFFFFF);
				}
			}
		}
		return returnImage;
	}
	
	public static byte[][] ArrayFromImage(BufferedImage image, int x0, int x1, int y0, int y1)
	{
		byte[][] returnArray = new byte[x1 - x0 + 1][y1 - y0 + 1];
		for (int i = x0; i <= x1; i++)
		{
			for (int j = y0; j <= y1; j++)
			{
				returnArray[i - x0][j - y0] = (byte) ColorUtil.RGBToGreyScale(image.getRGB(i, j));
			}
		}
		return returnArray;
	}
	
	public static byte[][] ThickenBitmap(int iteration, byte[][] tokenBitmap)
	{
		int width = tokenBitmap.length;
		int height = tokenBitmap[0].length;
		byte[][] returnArray = new byte[width][height];
		for (int j = 0; j < width; j++)
		{
			for (int k = 0; k < height; k++)
			{
				returnArray[j][k] = tokenBitmap[j][k];
			}
		}
		for (int i = 0; i < iteration; i++)
		{
			HashSet<Point> pointsToAdd = new HashSet<Point>();
			for (int j = 0; j < width; j++)
			{
				for (int k = 0; k < height; k++)
				{
					int temp = 0;
					for (int l = -1; l < 2; l++)
					{
						if (j + l < 0 || j + l >= width)
							continue;
						for (int m = -1; m < 2; m++)
						{
							if (k + m < 0 || k + m >= height)
								continue;	
							if (l != 0 && m != 0 && ByteUtil.ByteToInt(tokenBitmap[j + l][k + m]) < 255)
							{
								temp++;
							}
						}
					}
					if (temp > 0)
					{
						pointsToAdd.add(new Point(j, k));
					}
				}
			}
			Iterator<Point> pointIterator = pointsToAdd.iterator();
			while (pointIterator.hasNext())
			{
				Point p = pointIterator.next();
				returnArray[p.x][p.y] = (byte)0;
			}
		}
		return returnArray;
	}
	
	public static BufferedImage DilateImage(BufferedImage image, byte[][] se)
	{
		BufferedImage returnImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < image.getWidth(); i++)
		{
			for (int j = 0; j < image.getHeight(); j++)
			{
				returnImage.setRGB(i, j, 0xFFFFFFFF);
			}
		}
		for (int i = se.length / 2; i < image.getWidth() - Math.ceil((double)se.length / 2.0); i++)
		{
			for (int j = se[0].length / 2; j < image.getHeight() - Math.ceil((double)se[0].length / 2.0); j++)
			{
				int minIntensity = 255;
				for (int m = 0; m < se.length; m++)
				{
					for (int n = 0; n < se[m].length; n++)
					{
						if (se[m][n] != 0)
						{
							int pixelIntensity = ColorUtil.RGBToGreyScale(image.getRGB(i + m - se.length / 2, j + n - se[0].length / 2));
							if (pixelIntensity < minIntensity)
							{
								minIntensity = pixelIntensity;
							}
						}
					}
				}
				returnImage.setRGB(i, j, new Color(minIntensity, minIntensity, minIntensity).getRGB());
			}
		}
		return returnImage;
	}
	
	public static BufferedImage ErodeImage(BufferedImage image, byte[][] se)
	{
		BufferedImage returnImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < image.getWidth(); i++)
		{
			for (int j = 0; j < image.getHeight(); j++)
			{
				returnImage.setRGB(i, j, 0xFFFFFFFF);
			}
		}
		for (int i = se.length / 2; i < image.getWidth() - Math.ceil((double)se.length / 2.0); i++)
		{
			for (int j = se[0].length / 2; j < image.getHeight() - Math.ceil((double)se[0].length / 2.0); j++)
			{
				int maxIntensity = 0;
				for (int m = 0; m < se.length; m++)
				{
					for (int n = 0; n < se[m].length; n++)
					{
						if (se[m][n] != 0)
						{
							int pixelIntensity = ColorUtil.RGBToGreyScale(image.getRGB(i + m - se.length / 2, j + n - se[0].length / 2));
							if (pixelIntensity > maxIntensity)
							{
								maxIntensity = pixelIntensity;
							}
						}
					}
				}
				returnImage.setRGB(i, j, new Color(maxIntensity, maxIntensity, maxIntensity).getRGB());
			}
		}
		return returnImage;
	}
	
	public static BufferedImage Rotate(BufferedImage image, double rotationAngle)
	{
		int width = image.getWidth();
		int height = image.getHeight();
		double cos = Math.cos(rotationAngle);
		double sin = Math.sin(rotationAngle);
		BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

		for (int i = 0; i < width; i ++)
		{
			for (int j = 0; j < height; j++)
			{
				newImage.setRGB(i, j, 0xFFFFFFFF);
			}
		}
		for (int i = 0; i < width; i ++)
		{
			for (int j = 0; j < height; j++)
			{
				int rgb = image.getRGB(i, j);
				if (rgb != 0xFFFFFFFF)
				{
					int x = (int)Math.round((i - width / 2.0) * cos - (j - height / 2.0) * sin) + width / 2;
					int y = (int)Math.round((i - width / 2.0) * sin + (j - height / 2.0) * cos) + height / 2;
					if (x > -1 && x < width && y > -1 && y < height)
						newImage.setRGB(x, y, rgb);
				}
			}
		}
		return newImage;
	}
	
}
